/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
#include "tr_local.h"

// tr_shader.c -- this file deals with the parsing and definition of shaders

static char *s_shaderText;

// the shader is parsed into these global variables, then copied into
// dynamically allocated memory if it is valid.
static	shaderStage_t	stages[MAX_SHADER_STAGES];
static	shader_t		shader;
static	texModInfo_t	texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
static	qboolean		deferLoad;

#define FILE_HASH_SIZE		1024
static	shader_t*		hashTable[FILE_HASH_SIZE];

#define MAX_SHADERTEXT_HASH		2048
static char **shaderTextHashTable[MAX_SHADERTEXT_HASH];

char *__token;

float ParseFloat(char **text)
{
	__token = COM_ParseExt(text, qfalse);
	return (__token[0]) ? atof(__token) : 0.0;
}

/*
================
return a hash value for the filename
================
*/
static long generateHashValue(const char *fname, const int size)
{
    int		i;
    long	hash;
    char	letter;

    hash = 0;
    i = 0;
    while (fname[i] != '\0')
    {
        letter = tolower(fname[i]);
        if (letter =='.') break;				// don't include extension
        if (letter =='\\') letter = '/';		// damn path names
        if (letter == PATH_SEP) letter = '/';		// damn path names
        hash+=(long)(letter)*(i+119);
        i++;
    }
    hash = (hash ^ (hash >> 10) ^ (hash >> 20));
    hash &= (size-1);
    return hash;
}

void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset)
{
    char		strippedName[MAX_QPATH];
    int			hash;
    shader_t	*sh, *sh2;
    qhandle_t	h;

    sh = R_FindShaderByName(shaderName);
    if (sh == NULL || sh == tr.defaultShader)
    {
        h = RE_RegisterShaderLightMap(shaderName, 0);
        sh = R_GetShaderByHandle(h);
    }
    if (sh == NULL || sh == tr.defaultShader)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName);
        return;
    }

    sh2 = R_FindShaderByName(newShaderName);
    if (sh2 == NULL || sh2 == tr.defaultShader)
    {
        h = RE_RegisterShaderLightMap(newShaderName, 0);
        sh2 = R_GetShaderByHandle(h);
    }

    if (sh2 == NULL || sh2 == tr.defaultShader)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName);
        return;
    }

    // remap all the shaders with the given name
    // even tho they might have different lightmaps
    COM_StripExtension(shaderName, strippedName);
    hash = generateHashValue(strippedName, FILE_HASH_SIZE);
    for (sh = hashTable[hash]; sh; sh = sh->next)
    {
        if (Q_stricmp(sh->name, strippedName) == 0)
        {
            if (sh != sh2)
            {
                sh->remappedShader = sh2;
            }
            else
            {
                sh->remappedShader = NULL;
            }
        }
    }
    if (timeOffset)
    {
        sh2->timeOffset = atof(timeOffset);
    }
}

/*
===============
ParseVector
===============
*/
static qboolean ParseVector(char **text, int count, float *v)
{
    char	*token;
    int		i;

    // FIXME: spaces are currently required after parens, should change parseext...
    token = COM_ParseExt(text, qfalse);
    if (strcmp(token, "("))
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name);
        return qfalse;
    }

    for (i = 0 ; i < count ; i++)
    {
        token = COM_ParseExt(text, qfalse);
        if (!token[0])
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name);
            return qfalse;
        }
        v[i] = atof(token);
    }

    token = COM_ParseExt(text, qfalse);
    if (strcmp(token, ")"))
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name);
        return qfalse;
    }

    return qtrue;
}

/*
===============
NameToAFunc
===============
*/
static int NameToAFunc(char **text)
{
	__token = COM_ParseExt(text, qfalse);
	return
		(Q_strsame(__token, "GT0")) ? GLS_ATEST_GT_0 :
		(Q_strsame(__token, "LT128")) ? GLS_ATEST_LT_80 :
		(Q_strsame(__token, "GE128")) ? GLS_ATEST_GE_80 : 0
	;
}

/*
===============
NameToGenFunc
===============
*/
static genFunc_t NameToGenFunc(char **text)
{
	__token = COM_ParseExt(text, qfalse);
    return
		(Q_strsame(__token, "sin")) ? GF_SIN :
		(Q_strsame(__token, "square")) ? GF_SQUARE :
		(Q_strsame(__token, "triangle")) ? GF_TRIANGLE :
		(Q_strsame(__token, "sawtooth")) ? GF_SAWTOOTH :
		(Q_strsame(__token, "inversesawtooth")) ? GF_INVERSE_SAWTOOTH:
		(Q_strsame(__token, "noise")) ? GF_NOISE : GF_SIN
	;
}

/*
===================
ParseWaveForm
===================
*/
static void ParseWaveForm(char **text, waveForm_t *wave)
{
    wave->func = NameToGenFunc(text);
    wave->base = ParseFloat(text);
    wave->amplitude = ParseFloat(text);
    wave->phase = ParseFloat(text);
    wave->frequency = ParseFloat(text);
}

/*
===================
ParseTexMod
===================
*/
static void ParseTexMod(char *_text, shaderStage_t *stage)
{
    const char *token;
    char **text = &_text;
    texModInfo_t *tmi;

    if (stage->bundle[0].numTexMods == TR_MAX_TEXMODS)
    {
        Com_Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name);
        return;
    }

    tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods];
    stage->bundle[0].numTexMods++;

    token = COM_ParseExt(text, qfalse);
    if (Q_strsame(token, "turb"))
    {
        tmi->wave.base = ParseFloat(text);
        tmi->wave.amplitude = ParseFloat(text);
        tmi->wave.phase = ParseFloat(text);
        tmi->wave.frequency = ParseFloat(text);
        tmi->type = TMOD_TURBULENT;
    }
    else if (Q_strsame(token, "scale"))
    {
        tmi->scale[0] = ParseFloat(text);
        tmi->scale[1] = ParseFloat(text);
        tmi->type = TMOD_SCALE;
    }
    else if (Q_strsame(token, "scroll"))
    {
        tmi->scroll[0] = ParseFloat(text);
        tmi->scroll[1] = ParseFloat(text);
        tmi->type = TMOD_SCROLL;
    }
    else if (Q_strsame(token, "stretch"))
    {
        tmi->wave.func = NameToGenFunc(text);
        tmi->wave.base = ParseFloat(text);
        tmi->wave.amplitude = ParseFloat(text);
        tmi->wave.phase = ParseFloat(text);
        tmi->wave.frequency = ParseFloat(text);
        tmi->type = TMOD_STRETCH;
    }
    else if (Q_strsame(token, "transform"))
    {
        tmi->matrix[0][0] = ParseFloat(text);
        tmi->matrix[0][1] = ParseFloat(text);
        tmi->matrix[1][0] = ParseFloat(text);
        tmi->matrix[1][1] = ParseFloat(text);
        tmi->translate[0] = ParseFloat(text);
        tmi->translate[1] = ParseFloat(text);
        tmi->type = TMOD_TRANSFORM;
    }
    else if (Q_strsame(token, "rotate"))
    {
        tmi->rotateSpeed = ParseFloat(text);
        tmi->type = TMOD_ROTATE;
    }
    else if (Q_strsame(token, "entityTranslate"))
        tmi->type = TMOD_ENTITY_TRANSLATE;
    else
        CL_RefPrintf(PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name);
}

/*
===================
ParseStage
===================
*/
static qboolean ParseStage(shaderStage_t *stage, char **text)
{
    char *token;
    int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0;
    qboolean depthMaskExplicit = qfalse;

    stage->active = qtrue;

    while(1)
    {
        token = COM_ParseExt(text, qtrue);
        if (!token[0])
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: no matching '}' found\n");
            return qfalse;
        }

        if (token[0] == '}')
            break;
		// map <image>
        else if (Q_strsame(token, "map"))
        {
            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name);
                return qfalse;
            }

            if (Q_strsame(token, "$whiteimage"))
            {
                stage->bundle[0].image[0] = tr.whiteImage;
                continue;
            }
            else if (Q_strsame(token, "$lightmap"))
            {
                stage->bundle[0].isLightmap = qtrue;
                stage->bundle[0].image[0] = (shader.lightmapIndex < 0) ? tr.whiteImage : tr.lightmaps[shader.lightmapIndex];
                continue;
            }
            else
            {
                stage->bundle[0].image[0] = R_FindImageFile(token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT);
                if (!stage->bundle[0].image[0])
                {
                    CL_RefPrintf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
                    return qfalse;
                }
            }
        }
        else if(Q_strsame(token, "tgmap"))
        {
            char altname[MAX_QPATH];

            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameter for 'tgmap' keyword in shader '%s'\n", shader.name);
                return qfalse;
            }

            strcpy(altname, token);

            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameter for 'tgmap' keyword in shader '%s'\n", shader.name);
                return qfalse;
            }

            stage->bundle[0].image[0] = R_FindTexgenImage(altname, token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT);
            if (!stage->bundle[0].image[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
                return qfalse;
            }
        }
        // clampmap <name>
        else if (Q_strsame(token, "clampmap"))
        {
            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name);
                return qfalse;
            }

            stage->bundle[0].image[0] = R_FindImageFile(token, !shader.noMipMaps, !shader.noPicMip, GL_CLAMP);
            if (!stage->bundle[0].image[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
                return qfalse;
            }
        }
        // animMap <frequency> <image1> .... <imageN>
        else if (Q_strsame(token, "animMap"))
        {
            stage->bundle[0].imageAnimationSpeed = ParseFloat(text);
            while (1)
            {
                int num = stage->bundle[0].numImageAnimations;

                token = COM_ParseExt(text, qfalse);
                if (!token[0])
                    break;

                if (num < MAX_IMAGE_ANIMATIONS)
                {
                    stage->bundle[0].image[num] = R_FindImageFile(token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT);
                    if (!stage->bundle[0].image[num])
                    {
                        CL_RefPrintf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
                        return qfalse;
                    }
                    stage->bundle[0].numImageAnimations++;
                }
            }
        }
		// videoMap <video file>
        else if (Q_strsame(token, "videoMap"))
        {
            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name);
                return qfalse;
            }
            stage->bundle[0].videoMapHandle = CIN_PlayCinematic(token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader));
            if (stage->bundle[0].videoMapHandle != -1)
            {
                stage->bundle[0].isVideoMap = qtrue;
                stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle];
            }
        }
        // alphafunc <func>
        else if (Q_strsame(token, "alphaFunc"))
        {
            atestBits = NameToAFunc(text);
        }
        // depthFunc <func>
        else if (Q_strsame(token, "depthfunc"))
        {
            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name);
                return qfalse;
            }

            if (Q_strsame(token, "lequal"))
            {
                depthFuncBits = 0;
            }
            else if (Q_strsame(token, "equal"))
            {
                depthFuncBits = GLS_DEPTHFUNC_EQUAL;
            }
            else
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name);
                continue;
            }
        }
        // detail
        else if (Q_strsame(token, "detail"))
        {
            stage->isDetail = qtrue;
        }
        // blendfunc <srcFactor> <dstFactor>
        // or blendfunc <add|filter|blend>
        else if (Q_strsame(token, "blendfunc"))
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0] == 0)
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name);
                continue;
            }
            // check for "simple" blends first
            if (Q_strsame(token, "add"))
            {
                blendSrcBits = GLS_SRCBLEND_ONE;
                blendDstBits = GLS_DSTBLEND_ONE;
            }
            else if (Q_strsame(token, "filter"))
            {
                blendSrcBits = GLS_SRCBLEND_DST_COLOR;
                blendDstBits = GLS_DSTBLEND_ZERO;
            }
            else if (Q_strsame(token, "blend"))
            {
                blendSrcBits = GLS_SRCBLEND_SRC_ALPHA;
                blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
            }
            else
			// complex double blends
            {
                blendSrcBits =	(Q_strsame(token, "GL_ONE")) ? GLS_SRCBLEND_ONE :
								(Q_strsame(token, "GL_ZERO")) ? GLS_SRCBLEND_ZERO :
								(Q_strsame(token, "GL_DST_COLOR")) ? GLS_SRCBLEND_DST_COLOR :
								(Q_strsame(token, "GL_ONE_MINUS_DST_COLOR")) ? GLS_SRCBLEND_ONE_MINUS_DST_COLOR :
								(Q_strsame(token, "GL_SRC_ALPHA")) ? GLS_SRCBLEND_SRC_ALPHA :
								(Q_strsame(token, "GL_ONE_MINUS_SRC_ALPHA")) ? GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA :
								(Q_strsame(token, "GL_DST_ALPHA")) ? GLS_SRCBLEND_DST_ALPHA :
								(Q_strsame(token, "GL_ONE_MINUS_DST_ALPHA")) ? GLS_SRCBLEND_ONE_MINUS_DST_ALPHA :
								(Q_strsame(token, "GL_SRC_ALPHA_SATURATE")) ? GLS_SRCBLEND_ALPHA_SATURATE : GLS_SRCBLEND_ONE;

				token = COM_ParseExt(text, qfalse);
				if (token[0] == 0)
				{
					CL_RefPrintf(PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name);
					continue;
				}

                blendDstBits =	(Q_strsame(token, "GL_ONE")) ? GLS_DSTBLEND_ONE :
								(Q_strsame(token, "GL_ZERO")) ? GLS_DSTBLEND_ZERO :
								(Q_strsame(token, "GL_SRC_ALPHA")) ? GLS_DSTBLEND_SRC_ALPHA :
								(Q_strsame(token, "GL_ONE_MINUS_SRC_ALPHA")) ? GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA :
								(Q_strsame(token, "GL_DST_ALPHA")) ? GLS_DSTBLEND_DST_ALPHA :
								(Q_strsame(token, "GL_ONE_MINUS_DST_ALPHA")) ? GLS_DSTBLEND_ONE_MINUS_DST_ALPHA :
								(Q_strsame(token, "GL_SRC_COLOR")) ? GLS_DSTBLEND_SRC_COLOR :
								(Q_strsame(token, "GL_ONE_MINUS_SRC_COLOR")) ? GLS_DSTBLEND_ONE_MINUS_SRC_COLOR : GLS_DSTBLEND_ONE;
            }

            // clear depth mask for blended surfaces
            if (!depthMaskExplicit)
                depthMaskBits = 0;
        }
        // rgbGen
        else if (Q_strsame(token, "rgbGen"))
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0] == 0)
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name);
                continue;
            }

            if (Q_strsame(token, "wave"))
            {
                ParseWaveForm(text, &stage->rgbWave);
                stage->rgbGen = CGEN_WAVEFORM;
            }
            else if (Q_strsame(token, "const"))
            {
                ParseVector(text, 3, stage->constantColor);
                stage->rgbGen = CGEN_CONST;
            }
            else if (Q_strsame(token, "identity"))
                stage->rgbGen = CGEN_IDENTITY;
            else if (Q_strsame(token, "identityLighting"))
                stage->rgbGen = CGEN_IDENTITY_LIGHTING;
            else if (Q_strsame(token, "entity"))
                stage->rgbGen = CGEN_ENTITY;
            else if (Q_strsame(token, "oneMinusEntity"))
                stage->rgbGen = CGEN_ONE_MINUS_ENTITY;
            else if (Q_strsame(token, "vertex"))
            {
                stage->rgbGen = CGEN_VERTEX;
                if (stage->alphaGen == 0)
                    stage->alphaGen = AGEN_VERTEX;
            }
            else if (Q_strsame(token, "exactVertex"))
                stage->rgbGen = CGEN_EXACT_VERTEX;
            else if (Q_strsame(token, "lightingDiffuse"))
                stage->rgbGen = CGEN_LIGHTING_DIFFUSE;
            else if (Q_strsame(token, "oneMinusVertex"))
                stage->rgbGen = CGEN_ONE_MINUS_VERTEX;
            else
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name);
                continue;
            }
        }
        // alphaGen
        else if (Q_strsame(token, "alphaGen"))
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0] == 0)
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name);
                continue;
            }
            if (Q_strsame(token, "wave"))
            {
                ParseWaveForm(text, &stage->alphaWave);
                stage->alphaGen = AGEN_WAVEFORM;
            }
            else if (Q_strsame(token, "const"))
            {
                stage->constantColor[3] = ParseFloat(text);
                stage->alphaGen = AGEN_CONST;
            }
            else if (Q_strsame(token, "identity"))
                stage->alphaGen = AGEN_IDENTITY;
            else if (Q_strsame(token, "entity"))
                stage->alphaGen = AGEN_ENTITY;
            else if (Q_strsame(token, "oneMinusEntity"))
                stage->alphaGen = AGEN_ONE_MINUS_ENTITY;
            else if (Q_strsame(token, "vertex"))
                stage->alphaGen = AGEN_VERTEX;
            else if (Q_strsame(token, "lightingSpecular"))
                stage->alphaGen = AGEN_LIGHTING_SPECULAR;
            else if (Q_strsame(token, "oneMinusVertex"))
                stage->alphaGen = AGEN_ONE_MINUS_VERTEX;
            else if (Q_strsame(token, "portal"))
            {
                stage->alphaGen = AGEN_PORTAL;
                shader.portalRange = ParseFloat(text);
            }
            else
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name);
                continue;
            }
        }
        // tcGen <function>
        else if (Q_strsame(token, "texgen") || Q_strsame(token, "tcGen"))
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0] == 0)
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name);
                continue;
            }
            if (Q_strsame(token, "environment"))
                stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED;
            else if (Q_strsame(token, "lightmap"))
                stage->bundle[0].tcGen = TCGEN_LIGHTMAP;
            else if (Q_strsame(token, "texture") || Q_strsame(token, "base"))
                stage->bundle[0].tcGen = TCGEN_TEXTURE;
            else if (Q_strsame(token, "vector"))
            {
                ParseVector(text, 3, stage->bundle[0].tcGenVectors[0]);
                ParseVector(text, 3, stage->bundle[0].tcGenVectors[1]);
                stage->bundle[0].tcGen = TCGEN_VECTOR;
            }
            else
                CL_RefPrintf(PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name);
        }
        // tcMod <type> <...>
        else if (Q_strsame(token, "tcMod"))
        {
            char buffer[1024] = "";
            while (1)
            {
                token = COM_ParseExt(text, qfalse);
                if (token[0] == 0)
                    break;
                strcat(buffer, token);
                strcat(buffer, " ");
            }
            ParseTexMod(buffer, stage);
            continue;
        }
        // depthmask
        else if (Q_strsame(token, "depthwrite"))
        {
            depthMaskBits = GLS_DEPTHMASK_TRUE;
            depthMaskExplicit = qtrue;
            continue;
        }
        else
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name);
            return qfalse;
        }
    }

    // if cgen isn't explicitly specified, use either identity or identitylighting
    if(stage->rgbGen == CGEN_BAD)
		stage->rgbGen = (blendSrcBits == 0 || blendSrcBits == GLS_SRCBLEND_ONE || blendSrcBits == GLS_SRCBLEND_SRC_ALPHA) ? CGEN_IDENTITY_LIGHTING : CGEN_IDENTITY;

    // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending
    if(blendSrcBits == GLS_SRCBLEND_ONE && blendDstBits == GLS_DSTBLEND_ZERO)
    {
        blendDstBits = blendSrcBits = 0;
        depthMaskBits = GLS_DEPTHMASK_TRUE;
    }

    // decide which agens we can skip
    if (stage->alphaGen == CGEN_IDENTITY && (stage->rgbGen == CGEN_IDENTITY || stage->rgbGen == CGEN_LIGHTING_DIFFUSE))
        stage->alphaGen = AGEN_SKIP;

    // compute state bits
    stage->stateBits = depthMaskBits | blendSrcBits | blendDstBits | atestBits | depthFuncBits;

    return qtrue;
}

/*
===============
ParseDeform
 
deformVertexes wave <spread> <waveform> <base> <amplitude> <phase> <frequency>
deformVertexes normal <frequency> <amplitude>
deformVertexes move <vector> <waveform> <base> <amplitude> <phase> <frequency>
deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
deformVertexes projectionShadow
deformVertexes autoSprite
deformVertexes autoSprite2
deformVertexes text[0-7]
===============
*/
static void ParseDeform(char **text)
{
    char	*token;
    deformStage_t	*ds;

    token = COM_ParseExt(text, qfalse);
    if (token[0] == 0)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name);
        return;
    }

    if (shader.numDeforms == MAX_SHADER_DEFORMS)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name);
        return;
    }

    ds = &shader.deforms[ shader.numDeforms ];
    shader.numDeforms++;

    if (Q_strsame(token, "projectionShadow"))
    {
        ds->deformation = DEFORM_PROJECTION_SHADOW;
        return;
    }

    if (Q_strsame(token, "autosprite"))
    {
        ds->deformation = DEFORM_AUTOSPRITE;
        return;
    }

    if (Q_strsame(token, "autosprite2"))
    {
        ds->deformation = DEFORM_AUTOSPRITE2;
        return;
    }

    if (Q_strsamen(token, "text", 4))
    {
        int	 	n;

        n = token[4] - '0';
        if (n < 0 || n > 7)
        {
            n = 0;
        }
        ds->deformation = (deform_t)(DEFORM_TEXT0 + n);
        return;
    }

    if (Q_strsame(token, "bulge"))
    {
        token = COM_ParseExt(text, qfalse);
        if (token[0] == 0)
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name);
            return;
        }
        ds->bulgeWidth = atof(token);

        token = COM_ParseExt(text, qfalse);
        if (token[0] == 0)
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name);
            return;
        }
        ds->bulgeHeight = atof(token);

        token = COM_ParseExt(text, qfalse);
        if (token[0] == 0)
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name);
            return;
        }
        ds->bulgeSpeed = atof(token);

        ds->deformation = DEFORM_BULGE;
        return;
    }

    if (Q_strsame(token, "wave"))
    {
        token = COM_ParseExt(text, qfalse);
        if (token[0] == 0)
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name);
            return;
        }

        if (atof(token) != 0)
        {
            ds->deformationSpread = 1.0f / atof(token);
        }
        else
        {
            ds->deformationSpread = 100.0f;
            CL_RefPrintf(PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name);
        }

        ParseWaveForm(text, &ds->deformationWave);
        ds->deformation = DEFORM_WAVE;
        return;
    }

    if (Q_strsame(token, "normal"))
    {
        token = COM_ParseExt(text, qfalse);
        if (token[0] == 0)
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name);
            return;
        }
        ds->deformationWave.amplitude = atof(token);

        token = COM_ParseExt(text, qfalse);
        if (token[0] == 0)
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name);
            return;
        }
        ds->deformationWave.frequency = atof(token);

        ds->deformation = DEFORM_NORMALS;
        return;
    }

    if (Q_strsame(token, "move"))
    {
        int		i;

        for (i = 0 ; i < 3 ; i++)
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0] == 0)
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name);
                return;
            }
            ds->moveVector[i] = atof(token);
        }

        ParseWaveForm(text, &ds->deformationWave);
        ds->deformation = DEFORM_MOVE;
        return;
    }

    CL_RefPrintf(PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name);
}


/*
===============
ParseSkyParms
 
skyParms <outerbox> <cloudheight> <innerbox>
===============
*/
static void ParseSkyParms(char **text)
{
    char		*token;
    static char	*suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
    char		pathname[MAX_QPATH];
    int			i;

    // outerbox
    token = COM_ParseExt(text, qfalse);
    if (token[0] == 0)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name);
        return;
    }
    if (strcmp(token, "-"))
    {
        for (i=0 ; i<6 ; i++)
        {
            Com_sprintf(pathname, sizeof(pathname), "%s_%s.tga"
                         , token, suf[i]);
            shader.sky.outerbox[i] = R_FindImageFile((char *) pathname, qtrue, qtrue, GL_CLAMP);
            if (!shader.sky.outerbox[i])
            {
                shader.sky.outerbox[i] = tr.defaultImage;
            }
        }
    }

    // cloudheight
    token = COM_ParseExt(text, qfalse);
    if (token[0] == 0)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name);
        return;
    }
    shader.sky.cloudHeight = atof(token);
    if (!shader.sky.cloudHeight)
    {
        shader.sky.cloudHeight = 512;
    }
    R_InitSkyTexCoords(shader.sky.cloudHeight);


    // innerbox
    token = COM_ParseExt(text, qfalse);
    if (token[0] == 0)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name);
        return;
    }
    if (strcmp(token, "-"))
    {
        for (i=0 ; i<6 ; i++)
        {
            Com_sprintf(pathname, sizeof(pathname), "%s_%s.tga"
                         , token, suf[i]);
            shader.sky.innerbox[i] = R_FindImageFile((char *) pathname, qtrue, qtrue, GL_REPEAT);
            if (!shader.sky.innerbox[i])
            {
                shader.sky.innerbox[i] = tr.defaultImage;
            }
        }
    }

    shader.isSky = qtrue;
}


/*
=================
ParseSort
=================
*/
void ParseSort(char **text)
{
    char	*token;

    token = COM_ParseExt(text, qfalse);
    if (token[0] == 0)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name);
        return;
    }

    if (Q_strsame(token, "portal"))
    {
        shader.sort = SS_PORTAL;
    }
    else if (Q_strsame(token, "sky"))
    {
        shader.sort = SS_ENVIRONMENT;
    }
    else if (Q_strsame(token, "opaque"))
    {
        shader.sort = SS_OPAQUE;
    }
    else if (Q_strsame(token, "decal"))
    {
        shader.sort = SS_DECAL;
    }
    else if (Q_strsame(token, "seeThrough"))
    {
        shader.sort = SS_SEE_THROUGH;
    }
    else if (Q_strsame(token, "banner"))
    {
        shader.sort = SS_BANNER;
    }
    else if (Q_strsame(token, "additive"))
    {
        shader.sort = SS_BLEND1;
    }
    else if (Q_strsame(token, "nearest"))
    {
        shader.sort = SS_NEAREST;
    }
    else if (Q_strsame(token, "underwater"))
    {
        shader.sort = SS_UNDERWATER;
    }
    else
    {
        shader.sort = atof(token);
    }
}



// this table is also present in q3map

typedef struct
{
    char	*name;
    int		clearSolid, surfaceFlags, contents;
}
infoParm_t;

infoParm_t	infoParms[] =
{
	// server relevant contents
	{"water",		1,	0,	CONTENTS_WATER },
	{"slime",		1,	0,	CONTENTS_SLIME },		// mildly damaging
	{"lava",		1,	0,	CONTENTS_LAVA },		// very damaging
	{"playerclip",	1,	0,	CONTENTS_PLAYERCLIP },
	{"monsterclip",	1,	0,	CONTENTS_MONSTERCLIP },
	{"nodrop",		1,	0,	CONTENTS_NODROP },		// don't drop items or leave bodies (death fog, lava, etc)
	{"nonsolid",	1,	SURF_NONSOLID,	0},						// clears the solid flag

	// utility relevant attributes
	{"origin",		1,	0,	CONTENTS_ORIGIN },		// center of rotating brushes
	{"trans",		0,	0,	CONTENTS_TRANSLUCENT },	// don't eat contained surfaces
	{"detail",		0,	0,	CONTENTS_DETAIL },		// don't include in structural bsp
	{"structural",	0,	0,	CONTENTS_STRUCTURAL },	// force into structural bsp even if trnas
	{"areaportal",	1,	0,	CONTENTS_AREAPORTAL },	// divides areas
	{"clusterportal", 1,0,  CONTENTS_CLUSTERPORTAL },	// for bots
	{"donotenter",  1,  0,  CONTENTS_DONOTENTER },		// for bots

	{"fog",			1,	0,	CONTENTS_FOG},			// carves surfaces entering
	{"sky",			0,	SURF_SKY,		0 },		// emit light from an environment map
	{"lightfilter",	0,	SURF_LIGHTFILTER, 0 },		// filter light going through it
	{"alphashadow",	0,	SURF_ALPHASHADOW, 0 },		// test light on a per-pixel basis
	{"hint",		0,	SURF_HINT,		0 },		// use as a primary splitter

	// server attributes
	{"slick",		0,	SURF_SLICK,		0 },
	{"noimpact",	0,	SURF_NOIMPACT,	0 },		// don't make impact explosions or marks
	{"nomarks",		0,	SURF_NOMARKS,	0 },		// don't make impact marks, but still explode
	{"ladder",		0,	SURF_LADDER,	0 },
	{"nodamage",	0,	SURF_NODAMAGE,	0 },
	{"metalsteps",	0,	SURF_METALSTEPS,0 },
	{"flesh",		0,	SURF_FLESH,		0 },
	{"nosteps",		0,	SURF_NOSTEPS,	0 },

	// drawsurf attributes
	{"nodraw",		0,	SURF_NODRAW,	0 },	// don't generate a drawsurface (or a lightmap)
	{"pointlight",	0,	SURF_POINTLIGHT, 0 },	// sample lighting at vertexes
	{"nolightmap",	0,	SURF_NOLIGHTMAP,0 },	// don't generate a lightmap
	{"nodlight",	0,	SURF_NODLIGHT, 0 },		// don't ever add dynamic lights
	{"dust",		0,	SURF_DUST, 0}			// leave a dust trail when walking on this surface
};


/*
===============
ParseSurfaceParm
 
surfaceparm <name>
===============
*/
static void ParseSurfaceParm(char **text)
{
    char	*token;
    int		numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]);
    int		i;

    token = COM_ParseExt(text, qfalse);
    for (i = 0 ; i < numInfoParms ; i++)
    {
        if (Q_strsame(token, infoParms[i].name))
        {
            shader.surfaceFlags |= infoParms[i].surfaceFlags;
            shader.contentFlags |= infoParms[i].contents;
#if 0
            if (infoParms[i].clearSolid)
            {
                si->contents &= ~CONTENTS_SOLID;
            }
#endif
            break;
        }
    }
}

/*
=================
ParseShader
 
The current text pointer is at the explicit text definition of the
shader.  Parse it into the global shader variable.  Later functions
will optimize it.
=================
*/
static qboolean ParseShader(char **text)
{
    char *token;
    int s;

    s = 0;

    token = COM_ParseExt(text, qtrue);
    if (token[0] != '{')
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name);
        return qfalse;
    }

    while (1)
    {
        token = COM_ParseExt(text, qtrue);
        if (!token[0])
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name);
            return qfalse;
        }

        // end of shader definition
        if (token[0] == '}')
        {
            break;
        }
        // stage definition
        else if (token[0] == '{')
        {
            if (!ParseStage(&stages[s], text))
            {
                return qfalse;
            }
            stages[s].active = qtrue;
            s++;
            continue;
        }
        // skip stuff that only the QuakeEdRadient needs
        else if (Q_strsamen(token, "qer", 3))
        {
            SkipRestOfLine(text);
            continue;
        }
        // sun parms
        else if (Q_strsame(token, "q3map_sun"))
        {
            float	a, b;

            token = COM_ParseExt(text, qfalse);
            tr.sunLight[0] = atof(token);
            token = COM_ParseExt(text, qfalse);
            tr.sunLight[1] = atof(token);
            token = COM_ParseExt(text, qfalse);
            tr.sunLight[2] = atof(token);

            VectorNormalize(tr.sunLight);

            token = COM_ParseExt(text, qfalse);
            a = atof(token);
            VectorScale(tr.sunLight, a, tr.sunLight);

            token = COM_ParseExt(text, qfalse);
            a = atof(token);
            a = a / 180 * M_PI;

            token = COM_ParseExt(text, qfalse);
            b = atof(token);
            b = b / 180 * M_PI;

            tr.sunDirection[0] = cos(a) * cos(b);
            tr.sunDirection[1] = sin(a) * cos(b);
            tr.sunDirection[2] = sin(b);
        }
        else if (Q_strsame(token, "deformVertexes"))
        {
            ParseDeform(text);
            continue;
        }
        else if (Q_strsame(token, "tesssize"))
        {
            SkipRestOfLine(text);
            continue;
        }
        else if (Q_strsame(token, "clampTime"))
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0])
                shader.clampTime = atof(token);
        }
        // skip stuff that only the q3map needs
        else if (Q_strsamen(token, "q3map", 5))
        {
            SkipRestOfLine(text);
            continue;
        }
        // skip stuff that only q3map or the server needs
        else if (Q_strsame(token, "surfaceParm"))
        {
            ParseSurfaceParm(text);
            continue;
        }
        // no mip maps
        else if (Q_strsame(token, "nomipmaps"))
        {
            shader.noMipMaps = qtrue;
            shader.noPicMip = qtrue;
            continue;
        }
        // no picmip adjustment
        else if (Q_strsame(token, "nopicmip"))
        {
            shader.noPicMip = qtrue;
            continue;
        }
        // polygonOffset
        else if (Q_strsame(token, "polygonOffset"))
        {
            shader.polygonOffset = qtrue;
            continue;
        }
        // entityMergable, allowing sprite surfaces from multiple entities
        // to be merged into one batch.  This is a savings for smoke
        // puffs and blood, but can't be used for anything where the
        // shader calcs (not the surface function) reference the entity color or scroll
        else if (Q_strsame(token, "entityMergable"))
        {
            shader.entityMergable = qtrue;
            continue;
        }
        // fogParms
        else if (Q_strsame(token, "fogParms"))
        {
            if (!ParseVector(text, 3, shader.fogParms.color))
                return qfalse;

            token = COM_ParseExt(text, qfalse);
            if (!token[0])
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name);
                continue;
            }
            shader.fogParms.depthForOpaque = atof(token);

            // skip any old gradient directions
            SkipRestOfLine(text);
            continue;
        }
        // portal
        else if (Q_strsame(token, "portal"))
        {
            shader.sort = SS_PORTAL;
            continue;
        }
        // skyparms <cloudheight> <outerbox> <innerbox>
        else if (Q_strsame(token, "skyparms"))
        {
            ParseSkyParms(text);
            continue;
        }
        // light <value> determines flaring in q3map, not needed here
        else if (Q_strsame(token, "light"))
        {
            token = COM_ParseExt(text, qfalse);
            continue;
        }
        // cull <face>
        else if (Q_strsame(token, "cull"))
        {
            token = COM_ParseExt(text, qfalse);
            if (token[0] == 0)
            {
                CL_RefPrintf(PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name);
                continue;
            }

            if (Q_strsame(token, "none") || Q_strsame(token, "twosided") || Q_strsame(token, "disable"))
                shader.cullType = CT_TWO_SIDED;
            else if (Q_strsame(token, "back") || Q_strsame(token, "backside") || Q_strsame(token, "backsided"))
                shader.cullType = CT_BACK_SIDED;
            else
                CL_RefPrintf(PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name);
            continue;
        }
        // sort
        else if (Q_strsame(token, "sort"))
        {
            ParseSort(text);
            continue;
        }
        else
        {
            CL_RefPrintf(PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name);
            return qfalse;
        }
    }

    //
    // ignore shaders that don't have any stages, unless it is a sky or fog
    //
    if (s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG))
        return qfalse;

    shader.explicitlyDefined = qtrue;

    return qtrue;
}

/*
========================================================================================
 
SHADER OPTIMIZATION AND FOGGING
 
========================================================================================
*/

/*
===================
ComputeStageIteratorFunc
 
See if we can use on of the simple fastpath stage functions,
otherwise set to the generic stage function
===================
*/
static void ComputeStageIteratorFunc(void)
{
    shader.optimalStageIteratorFunc = RB_StageIteratorGeneric;

    // see if this should go into the sky path
    if (shader.isSky)
    {
        shader.optimalStageIteratorFunc = RB_StageIteratorSky;
        goto done;
    }

    if (r_ignoreFastPath->integer)
        return;

    // see if this can go into the vertex lit fast path
    if (shader.numUnfoggedPasses == 1)
    {
        if (stages[0].rgbGen == CGEN_LIGHTING_DIFFUSE)
        {
            if (stages[0].alphaGen == AGEN_IDENTITY)
            {
                if (stages[0].bundle[0].tcGen == TCGEN_TEXTURE)
                {
                    if (!shader.polygonOffset)
                    {
                        if (!stages[0].multitextureEnv)
                        {
                            if (!shader.numDeforms)
                            {
                                shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTexture;
                                goto done;
                            }
                        }
                    }
                }
            }
        }
    }

    // see if this can go into an optimized LM, multitextured path
    if (shader.numUnfoggedPasses == 1)
    {
        if ((stages[0].rgbGen == CGEN_IDENTITY) && (stages[0].alphaGen == AGEN_IDENTITY))
        {
            if (stages[0].bundle[0].tcGen == TCGEN_TEXTURE &&
                    stages[0].bundle[1].tcGen == TCGEN_LIGHTMAP)
            {
                if (!shader.polygonOffset)
                {
                    if (!shader.numDeforms)
                    {
                        if (stages[0].multitextureEnv)
                        {
                            shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitexture;
                            goto done;
                        }
                    }
                }
            }
        }
    }

done:
    return;
}

typedef struct
{
    int		blendA;
    int		blendB;

    int		multitextureEnv;
    int		multitextureBlend;
}
collapse_t;

static collapse_t	collapse[] =
{
	{ 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GL_MODULATE, 0 },
	{ 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GL_MODULATE, 0 },
	{ GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
	{ GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
	{ GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
	{ GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
	{ 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GL_ADD, 0 },
	{ GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE },
	{ -1 }
};

/*
================
CollapseMultitexture
 
Attempt to combine two stages into a single multitexture stage
FIXME: I think modulated add + modulated add collapses incorrectly
=================
*/
static qboolean CollapseMultitexture(void)
{
    int abits, bbits;
    int i, stage, converted = 0;
    textureBundle_t tmpBundle;

	shaderStage_t *stage1, *stage2;

    if (!GL_multitexture)
        return qfalse;

	for(stage=0; stage<MAX_SHADER_STAGES-1; stage++)
	{
		stage1 = &stages[stage];
		stage2 = &stages[stage+1];

		if(!stage1->active || !stage2->active)
			break;

	    // on voodoo2, don't combine same tmus
		if (glConfig.driverType == GLDRV_VOODOO)
			if(stage1->bundle[0].image[0]->TMU == stage2->bundle[0].image[0]->TMU)
				continue;

		abits = stage1->stateBits;
		bbits = stage2->stateBits;
		
		// make sure that both stages have identical state other than blend modes
		if ((abits & ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE)) != (bbits & ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE)))
			continue;

		abits &= (GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);
	    bbits &= (GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);

		// search for a valid multitexture blend function
		for (i = 0; collapse[i].blendA != -1 ; i++)
		{
			if (abits == collapse[i].blendA && bbits == collapse[i].blendB)
				break;
		}

		// nothing found
		if (collapse[i].blendA == -1)
			continue;

		// GL_ADD is a separate extension
		if (collapse[i].multitextureEnv == GL_ADD && !GL_EXT_texture_env_add_supported)
			continue;

		// make sure waveforms have identical parameters
		if ((stage1->rgbGen != stage2->rgbGen) || (stage1->alphaGen != stage2->alphaGen))
			continue;

		// an add collapse can only have identity colors
		if (collapse[i].multitextureEnv == GL_ADD && stage1->rgbGen != CGEN_IDENTITY)
			continue;

		if (stage1->rgbGen == CGEN_WAVEFORM)
			if (memcmp(&stage1->rgbWave, &stage2->rgbWave, sizeof(stage1->rgbWave)))
				continue;

		if (stage1->alphaGen == CGEN_WAVEFORM)
			if (memcmp(&stage1->alphaWave, &stage2->alphaWave, sizeof(stage1->alphaWave)))
				continue;

		converted++;

		// make sure that lightmaps are in bundle 1 for 3dfx
		if (stage1->bundle[0].isLightmap)
		{
			tmpBundle = stage1->bundle[0];
			stage1->bundle[0] = stage2->bundle[0];
			stage1->bundle[1] = tmpBundle;
		}
		else
			stage1->bundle[1] = stage2->bundle[0];

		// set the new blend state bits
		stage1->multitextureEnv = collapse[i].multitextureEnv;
		stage1->stateBits &= ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);
		stage1->stateBits |= collapse[i].multitextureBlend;
		
		// move down subsequent shaders
		memmove(stage2, &stages[stage+2], sizeof(stages[0]) * (MAX_SHADER_STAGES - 2));
		Com_Memset(&stages[MAX_SHADER_STAGES-1], 0, sizeof(stages[0]));
	}

    return (converted > 0);
}

/*
=============
 
FixRenderCommandList
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493
Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated
but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces
to be rendered with bad shaders. To fix this, need to go through all render commands and fix
sortedIndex.
==============
*/
static void FixRenderCommandList(int newShader)
{
    renderCommandList_t	*cmdList = &backEndData[tr.smpFrame]->commands;

    if(cmdList)
    {
        const void *curCmd = cmdList->cmds;

        while (1)
        {
            switch (*(const int *)curCmd)
            {
            case RC_SET_COLOR:
            {
                const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd;
                curCmd = (const void *)(sc_cmd + 1);
                break;
            }
            case RC_STRETCH_PIC:
            {
                const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd;
                curCmd = (const void *)(sp_cmd + 1);
                break;
            }
            case RC_DRAW_SURFS:
            {
                int i;
                drawSurf_t	*drawSurf;
                shader_t	*shader;
                int			fogNum;
                int			entityNum;
                int			dlightMap;
                int			sortedIndex;
                const drawSurfsCommand_t *ds_cmd =  (const drawSurfsCommand_t *)curCmd;

                for(i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++)
                {
                    R_DecomposeSort(drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap);
                    sortedIndex = ((drawSurf->sort >> QSORT_SHADERNUM_SHIFT) & (MAX_SHADERS-1));
                    if(sortedIndex >= newShader)
                    {
                        sortedIndex++;
                        drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | (fogNum << QSORT_FOGNUM_SHIFT) | (int)dlightMap;
                    }
                }
                curCmd = (const void *)(ds_cmd + 1);
                break;
            }
            case RC_DRAW_BUFFER:
            {
                const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd;
                curCmd = (const void *)(db_cmd + 1);
                break;
            }
            case RC_SWAP_BUFFERS:
            {
                const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd;
                curCmd = (const void *)(sb_cmd + 1);
                break;
            }
            case RC_END_OF_LIST:
            default:
                return;
            }
        }
    }
}

/*
==============
SortNewShader
 
Positions the most recently created shader in the tr.sortedShaders[]
array so that the shader->sort key is sorted reletive to the other
shaders.
 
Sets shader->sortedIndex
==============
*/
static void SortNewShader(void)
{
    int		i;
    float	sort;
    shader_t	*newShader;

    newShader = tr.shaders[ tr.numShaders - 1 ];
    sort = newShader->sort;

    for (i = tr.numShaders - 2 ; i >= 0 ; i--)
    {
        if (tr.sortedShaders[ i ]->sort <= sort)
        {
            break;
        }
        tr.sortedShaders[i+1] = tr.sortedShaders[i];
        tr.sortedShaders[i+1]->sortedIndex++;
    }

    // Arnout: fix rendercommandlist
    // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493
    FixRenderCommandList(i+1);

    newShader->sortedIndex = i+1;
    tr.sortedShaders[i+1] = newShader;
}


/*
====================
GeneratePermanentShader
====================
*/
static shader_t *GeneratePermanentShader(void)
{
    shader_t	*newShader;
    int			i, b;
    int			size, hash;

    if (tr.numShaders == MAX_SHADERS)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n");
        return tr.defaultShader;
    }

    newShader = (shader_t *)Hunk_Alloc(sizeof(shader_t), h_low);

    *newShader = shader;

    if (shader.sort <= SS_OPAQUE)
    {
        newShader->fogPass = FP_EQUAL;
    }
    else if (shader.contentFlags & CONTENTS_FOG)
    {
        newShader->fogPass = FP_LE;
    }

    tr.shaders[ tr.numShaders ] = newShader;
    newShader->index = tr.numShaders;

    tr.sortedShaders[ tr.numShaders ] = newShader;
    newShader->sortedIndex = tr.numShaders;

    tr.numShaders++;

    for (i = 0 ; i < newShader->numUnfoggedPasses ; i++)
    {
        if (!stages[i].active)
        {
            break;
        }
        newShader->stages[i] = (shaderStage_t *)Hunk_Alloc(sizeof(stages[i]), h_low);
        *newShader->stages[i] = stages[i];

        for (b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++)
        {
            size = newShader->stages[i]->bundle[b].numTexMods * sizeof(texModInfo_t);
            newShader->stages[i]->bundle[b].texMods = (texModInfo_t *)Hunk_Alloc(size, h_low);
            Com_Memcpy(newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size);
        }
    }

    SortNewShader();

    hash = generateHashValue(newShader->name, FILE_HASH_SIZE);
    newShader->next = hashTable[hash];
    hashTable[hash] = newShader;

    return newShader;
}

/*
=================
VertexLightingCollapse
 
If vertex lighting is enabled, only render a single
pass, trying to guess which is the correct one to best aproximate
what it is supposed to look like.
=================
*/
static void VertexLightingCollapse(void)
{
    int		stage;
    shaderStage_t	*bestStage;
    int		bestImageRank;
    int		rank;

    // if we aren't opaque, just use the first pass
    if (shader.sort == SS_OPAQUE)
    {

        // pick the best texture for the single pass
        bestStage = &stages[0];
        bestImageRank = -999999;

        for (stage = 0; stage < MAX_SHADER_STAGES; stage++)
        {
            shaderStage_t *pStage = &stages[stage];

            if (!pStage->active)
                break;
            rank = 0;

            if (pStage->bundle[0].isLightmap)
                rank -= 100;
            if (pStage->bundle[0].tcGen != TCGEN_TEXTURE)
                rank -= 5;
            if (pStage->bundle[0].numTexMods)
                rank -= 5;
            if (pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING)
                rank -= 3;

            if (rank > bestImageRank )
            {
                bestImageRank = rank;
                bestStage = pStage;
            }
        }

        stages[0].bundle[0] = bestStage->bundle[0];
        stages[0].stateBits &= ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);
        stages[0].stateBits |= GLS_DEPTHMASK_TRUE;
        if (shader.lightmapIndex == LIGHTMAP_NONE)
            stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
        else
            stages[0].rgbGen = CGEN_EXACT_VERTEX;
        stages[0].alphaGen = AGEN_SKIP;
    }
    else
    {
        // don't use a lightmap (tesla coils)
        if (stages[0].bundle[0].isLightmap)
            stages[0] = stages[1];

        // if we were in a cross-fade cgen, hack it to normal
        if (stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY)
            stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
        if ((stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH) && (stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH))
            stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
        if ((stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH) && (stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH))
            stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
    }

    for (stage = 1; stage < MAX_SHADER_STAGES; stage++)
    {
        shaderStage_t *pStage = &stages[stage];

        if (!pStage->active)
            break;

        Com_Memset(pStage, 0, sizeof(*pStage));
    }
}

/*
=========================
FinishShader
 
Returns a freshly allocated shader with all the needed info
from the current global working shader
=========================
*/
static shader_t *FinishShader(void)
{
    int stage;
    qboolean		hasLightmapStage = qfalse;
    qboolean		vertexLightmap = qfalse;
	shader_t		*pShader;

    // set sky stuff appropriate
    if (shader.isSky)
        shader.sort = SS_ENVIRONMENT;

    // set polygon offset
    if (shader.polygonOffset && !shader.sort)
        shader.sort = SS_DECAL;

    // set appropriate stage information
    for (stage = 0; stage < MAX_SHADER_STAGES; stage++)
    {
        shaderStage_t *pStage = &stages[stage];

        if (!pStage->active)
            break;

        // check for a missing texture
        if (!pStage->bundle[0].image[0])
        {
            CL_RefPrintf(PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name);
            pStage->active = qfalse;
            continue;
        }

        // ditch this stage if it's detail and detail textures are disabled
        if (pStage->isDetail && !r_detailTextures->integer)
        {
            if (stage < (MAX_SHADER_STAGES - 1))
            {
                memmove(pStage, pStage + 1, sizeof(*pStage) * (MAX_SHADER_STAGES - stage - 1));
                Com_Memset( pStage + 1, 0, sizeof(*pStage));
            }
            continue;
        }

        // default texture coordinate generation
        if (pStage->bundle[0].isLightmap)
        {
            if (pStage->bundle[0].tcGen == TCGEN_BAD)
                pStage->bundle[0].tcGen = TCGEN_LIGHTMAP;
            hasLightmapStage = qtrue;
        }
        else if (pStage->bundle[0].tcGen == TCGEN_BAD)
                pStage->bundle[0].tcGen = TCGEN_TEXTURE;

        // determine sort order and fog color adjustment
        if ((pStage->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS)) && (stages[0].stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS)))
        {
            int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS;
            int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS;

            // fog color adjustment only works for blend modes that have a contribution
            // that aproaches 0 as the modulate values aproach 0 --
            // GL_ONE, GL_ONE
            // GL_ZERO, GL_ONE_MINUS_SRC_COLOR
            // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA

            // modulate, additive
            if (((blendSrcBits == GLS_SRCBLEND_ONE) && (blendDstBits == GLS_DSTBLEND_ONE)) || ((blendSrcBits == GLS_SRCBLEND_ZERO) && (blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR)))
                pStage->adjustColorsForFog = ACFF_MODULATE_RGB;
            // strict blend
            else if ((blendSrcBits == GLS_SRCBLEND_SRC_ALPHA) && (blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA))
                pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA;
            // premultiplied alpha
            else if ((blendSrcBits == GLS_SRCBLEND_ONE) && (blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA))
                pStage->adjustColorsForFog = ACFF_MODULATE_RGBA;

            // don't screw with sort order if this is a portal or environment
            if (!shader.sort)
                shader.sort = (pStage->stateBits & GLS_DEPTHMASK_TRUE) ? SS_SEE_THROUGH : SS_BLEND0;
        }
    }

    // there are times when you will need to manually apply a sort to
    // opaque alpha tested shaders that have later blend passes
    if (!shader.sort)
        shader.sort = SS_OPAQUE;

    // if we are in r_vertexLight mode, never use a lightmap texture
    if (stage > 1 && ((r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2))
    {
        VertexLightingCollapse();
        stage = 1;
        hasLightmapStage = qfalse;
    }

    // look for multitexture potential
    if (stage > 1 && CollapseMultitexture())
        stage--;

    if (shader.lightmapIndex >= 0 && !hasLightmapStage)
    {
        if (vertexLightmap)
            CL_RefPrintf(PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name);
        else
        {
            CL_RefPrintf(PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name);
            shader.lightmapIndex = LIGHTMAP_NONE;
        }
    }

    // compute number of passes
    shader.numUnfoggedPasses = stage;

    // fogonly shaders don't have any normal passes
    if (stage == 0)
        shader.sort = SS_FOG;

    // determine which stage iterator function is appropriate
    ComputeStageIteratorFunc();

	pShader = GeneratePermanentShader();

    return pShader;
}

//========================================================================================

/*
====================
FindShaderInShaderText
 
Scans the combined text description of all the shader files for
the given shader name.
 
return NULL if not found
 
If found, it will return a valid shader
=====================
*/
static char *FindShaderInShaderText(const char *shadername)
{

    char *token, *p;

    int i, hash;

    hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH);

    for (i = 0; shaderTextHashTable[hash][i]; i++)
    {
        p = shaderTextHashTable[hash][i];
        token = COM_ParseExt(&p, qtrue);
        if (Q_strsame(token, shadername))
            return p;
    }

    p = s_shaderText;

    if (!p)
        return NULL;

    // look for label
    while (1)
    {
        token = COM_ParseExt(&p, qtrue);
        if (token[0] == 0)
            break;

        if (Q_strsame(token, shadername))
            return p;
        else
            SkipBracedSection(&p);
    }

    return NULL;
}


/*
==================
R_FindShaderByName
 
Will always return a valid shader, but it might be the
default shader if the real one can't be found.
==================
*/
shader_t *R_FindShaderByName(const char *name)
{
    char		strippedName[MAX_QPATH];
    int			hash;
    shader_t	*sh;

    if (name==NULL || name[0] == 0)
        return tr.defaultShader;

    COM_StripExtension(name, strippedName);

    hash = generateHashValue(strippedName, FILE_HASH_SIZE);

    //
    // see if the shader is already loaded
    //
    for (sh=hashTable[hash]; sh; sh=sh->next)
    {
        // NOTE: if there was no shader or image available with the name strippedName
        // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
        // have to check all default shaders otherwise for every call to R_FindShader
        // with that same strippedName a new default shader is created.
        if (Q_stricmp(sh->name, strippedName) == 0)
            return sh;
    }

    return tr.defaultShader;
}


/*
===============
R_FindShader
 
Will always return a valid shader, but it might be the
default shader if the real one can't be found.
 
In the interest of not requiring an explicit shader text entry to
be defined for every single image used in the game, three default
shader behaviors can be auto-created for any image:
 
If lightmapIndex == LIGHTMAP_NONE, then the image will have
dynamic diffuse lighting applied to it, as apropriate for most
entity skin surfaces.
 
If lightmapIndex == LIGHTMAP_2D, then the image will be used
for 2D rendering unless an explicit shader is found
 
If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use
the vertex rgba modulate values, as apropriate for misc_model
pre-lit surfaces.
 
Other lightmapIndex values will have a lightmap stage created
and src*dest blending applied with the texture, as apropriate for
most world construction surfaces.
 
===============
*/
glShader_t glShader_noLightmap =
{
	"varying vec3 C_Lighting_Diffuse;\n"
	"\n"
	"uniform vec3 ambientLight;\n"
	"uniform vec3 directedLight;\n"
	"uniform vec3 lightDir;\n"
    "\n"
    "void main()\n"
	"{\n"
    "	gl_Position = ftransform();\n"
    "	C_Lighting_Diffuse = ambientLight + clamp((dot(gl_Normal, lightDir) * directedLight), 0.0, 1.0);\n"
    "	gl_TexCoord[0] = gl_MultiTexCoord0;\n"
    "}\n",
	"varying vec3 C_Lighting_Diffuse;\n"
	"\n"
    "uniform sampler2D Texture0;\n"
    "\n"
    "void main()\n"
	"{\n"
    "	gl_FragColor = vec4(C_Lighting_Diffuse, 1.0) * texture2D(Texture0, gl_TexCoord[0].xy);\n"
    "}\n",
	"glShader_noLightmap",
	GLS_DEFAULT,
	C_Lighting_Diffuse, T_Texture
};

glShader_t glShader_lightmapByVertex =
{
    "void main()\n"
	"{\n"
    "	gl_Position = ftransform();\n"
    "	gl_FrontColor = gl_Color;\n"
    "	gl_TexCoord[0] = gl_MultiTexCoord0;\n"
    "}\n",
    "uniform sampler2D Texture0;\n"
    "\n"
    "void main()\n"
	"{\n"
    "	gl_FragColor = gl_Color * texture2D(Texture0, gl_TexCoord[0].xy);\n"
    "}\n",
	"glShader_lightmapByVertex",
	GLS_DEFAULT,
	C_Exact_Vertex, T_Texture
};

glShader_t glShader_lightmapWhiteImage =
{
	"varying vec3 C_Identity_Lighting;\n"
	"\n"
	"uniform float identityLight;\n"
	"\n"
    "void main()\n"
	"{\n"
    "	gl_Position = ftransform();\n"
    "	gl_TexCoord[0] = gl_MultiTexCoord0;\n"
	"	C_Identity_Lighting = vec3(identityLight);\n"
    "}\n",
	"varying vec3 C_Identity_Lighting;\n"
	"\n"
    "uniform sampler2D Texture0;\n"
	"uniform sampler2D Texture1;\n" // shutup
    "\n"
    "void main()\n"
	"{\n"
    "	gl_FragColor = (vec4(C_Identity_Lighting, 1.0) * texture2D(Texture0, gl_TexCoord[0].xy));\n"
    "}\n",
	"glShader_lightmapWhiteImage",
	GLS_DEFAULT,
	C_Identity_Lighting, T_Texture
};

glShader_t glShader_lightmapNormal =
{
    "void main()\n"
	"{\n"
    "	gl_Position = ftransform();\n"
    "	gl_TexCoord[0] = gl_MultiTexCoord0;\n"
    "	gl_TexCoord[1] = gl_MultiTexCoord1;\n"
    "}\n",
    "uniform sampler2D Texture0;\n"
	"uniform sampler2D Texture1;\n"
    "\n"
    "void main()\n"
	"{\n"
    "	gl_FragColor = texture2D(Texture1, gl_TexCoord[1].xy) * texture2D(Texture0, gl_TexCoord[0].xy);\n"
    "}\n",
	"glShader_lightmapNormal",
	GLS_DEFAULT,
	0, T_Texture | T_LightMap
};

shader_t *R_FindShader(const char *name, int lightmapIndex, qboolean mipRawImage)
{
    char		strippedName[MAX_QPATH];
    char		fileName[MAX_QPATH];
    int			i, hash;
    char		*shaderText;
    image_t		*image;
    shader_t	*sh;

    if (name[0] == 0)
        return tr.defaultShader;

    // use (fullbright) vertex lighting if the bsp file doesn't have
    // lightmaps
    if (lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps)
        lightmapIndex = LIGHTMAP_BY_VERTEX;

    COM_StripExtension(name, strippedName);

    hash = generateHashValue(strippedName, FILE_HASH_SIZE);

    //
    // see if the shader is already loaded
    //
    for (sh = hashTable[hash]; sh; sh = sh->next)
    {
        // NOTE: if there was no shader or image available with the name strippedName
        // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
        // have to check all default shaders otherwise for every call to R_FindShader
        // with that same strippedName a new default shader is created.
        if ((sh->lightmapIndex == lightmapIndex || sh->defaultShader) && Q_strsame(sh->name, strippedName))
            return sh;
    }

    // make sure the render thread is stopped, because we are probably
    // going to have to upload an image
    if (r_smp->integer)
        R_SyncRenderThread();

    // clear the global shader
    Com_Memset(&shader, 0, sizeof(shader));
    Com_Memset(&stages, 0, sizeof(stages));
    Q_strncpyz(shader.name, strippedName, sizeof(shader.name));
    shader.lightmapIndex = lightmapIndex;
    for (i = 0 ; i < MAX_SHADER_STAGES ; i++)
        stages[i].bundle[0].texMods = texMods[i];

    // FIXME: set these "need" values apropriately
    shader.needsNormal = qtrue;
    shader.needsST1 = qtrue;
    shader.needsST2 = qtrue;
    shader.needsColor = qtrue;

    // attempt to define shader from an explicit parameter file
    shaderText = FindShaderInShaderText(strippedName);
    if (shaderText)
    {
        // enable this when building a pak file to get a global list
        // of all explicit shaders
        if (r_printShaders->integer)
            CL_RefPrintf(PRINT_ALL, "*SHADER* %s\n", name);

        if (!ParseShader(&shaderText))
            shader.defaultShader = qtrue;

        sh = FinishShader();
        return sh;
    }

    // if not defined in the in-memory shader descriptions,
    // look for a single TGA, BMP, or PCX
    Q_strncpyz(fileName, name, sizeof(fileName));
    COM_DefaultExtension(fileName, sizeof(fileName), ".tga");
    image = R_FindImageFile(fileName, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP);
    if (!image)
    {
        CL_RefPrintf(PRINT_DEVELOPER, "Couldn't find image for shader %s\n", name);
        shader.defaultShader = qtrue;
        return FinishShader();
    }

    // create the default shading commands
    if (shader.lightmapIndex == LIGHTMAP_NONE)
    {
        // dynamic colors at vertexes
        stages[0].bundle[0].image[0] = image;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
        stages[0].stateBits = GLS_DEFAULT;

		shader.glShader = &glShader_noLightmap;
    }
    else if (shader.lightmapIndex == LIGHTMAP_BY_VERTEX)
    {
        // explicit colors at vertexes
        stages[0].bundle[0].image[0] = image;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_EXACT_VERTEX;
        stages[0].alphaGen = AGEN_SKIP;
        stages[0].stateBits = GLS_DEFAULT;

		shader.glShader = &glShader_lightmapByVertex;
    }
    else if (shader.lightmapIndex == LIGHTMAP_2D)
    {
        // GUI elements
        stages[0].bundle[0].image[0] = image;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_VERTEX;
        stages[0].alphaGen = AGEN_VERTEX;
        stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
                              GLS_SRCBLEND_SRC_ALPHA |
                              GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
    }
    else if (shader.lightmapIndex == LIGHTMAP_WHITEIMAGE)
    {
        // fullbright level
        stages[0].bundle[0].image[0] = tr.whiteImage;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
        stages[0].stateBits = GLS_DEFAULT;

        stages[1].bundle[0].image[0] = image;
        stages[1].active = qtrue;
        stages[1].rgbGen = CGEN_IDENTITY;
        stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;

		shader.glShader = &glShader_lightmapWhiteImage;
    }
    else
    {
        // two pass lightmap
        stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
        stages[0].bundle[0].isLightmap = qtrue;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_IDENTITY;	// lightmaps are scaled on creation
        // for identitylight
        stages[0].stateBits = GLS_DEFAULT;

        stages[1].bundle[0].image[0] = image;
        stages[1].active = qtrue;
        stages[1].rgbGen = CGEN_IDENTITY;
        stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;

		shader.glShader = &glShader_lightmapNormal;
    }

    return FinishShader();
}

qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage)
{
    int			i, hash;
    shader_t	*sh;

    hash = generateHashValue(name, FILE_HASH_SIZE);

    //
    // see if the shader is already loaded
    //
    for (sh=hashTable[hash]; sh; sh=sh->next)
    {
        // NOTE: if there was no shader or image available with the name strippedName
        // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
        // have to check all default shaders otherwise for every call to R_FindShader
        // with that same strippedName a new default shader is created.
        if ((sh->lightmapIndex == lightmapIndex || sh->defaultShader) && Q_strsame(sh->name, name))
            return sh->index;
    }

    // make sure the render thread is stopped, because we are probably
    // going to have to upload an image
    if (r_smp->integer)
        R_SyncRenderThread();

    // clear the global shader
    Com_Memset(&shader, 0, sizeof(shader));
    Com_Memset(&stages, 0, sizeof(stages));
    Q_strncpyz(shader.name, name, sizeof(shader.name));
    shader.lightmapIndex = lightmapIndex;
    for (i = 0 ; i < MAX_SHADER_STAGES ; i++)
        stages[i].bundle[0].texMods = texMods[i];

    // FIXME: set these "need" values apropriately
    shader.needsNormal = qtrue;
    shader.needsST1 = qtrue;
    shader.needsST2 = qtrue;
    shader.needsColor = qtrue;

    //
    // create the default shading commands
    //
    if (shader.lightmapIndex == LIGHTMAP_NONE)
    {
        // dynamic colors at vertexes
        stages[0].bundle[0].image[0] = image;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
        stages[0].stateBits = GLS_DEFAULT;
		shader.glShader = &glShader_noLightmap;
    }
    else if (shader.lightmapIndex == LIGHTMAP_BY_VERTEX)
    {
        // explicit colors at vertexes
        stages[0].bundle[0].image[0] = image;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_EXACT_VERTEX;
        stages[0].alphaGen = AGEN_SKIP;
        stages[0].stateBits = GLS_DEFAULT;
		shader.glShader = &glShader_lightmapByVertex;
    }
    else if (shader.lightmapIndex == LIGHTMAP_2D)
    {
        // GUI elements
        stages[0].bundle[0].image[0] = image;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_VERTEX;
        stages[0].alphaGen = AGEN_VERTEX;
        stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
                              GLS_SRCBLEND_SRC_ALPHA |
                              GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
    }
    else if (shader.lightmapIndex == LIGHTMAP_WHITEIMAGE)
    {
        // fullbright level
        stages[0].bundle[0].image[0] = tr.whiteImage;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
        stages[0].stateBits = GLS_DEFAULT;

        stages[1].bundle[0].image[0] = image;
        stages[1].active = qtrue;
        stages[1].rgbGen = CGEN_IDENTITY;
        stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;

		shader.glShader = &glShader_lightmapWhiteImage;
    }
    else
    {
        // two pass lightmap
        stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
        stages[0].bundle[0].isLightmap = qtrue;
        stages[0].active = qtrue;
        stages[0].rgbGen = CGEN_IDENTITY;	// lightmaps are scaled on creation
        // for identitylight
        stages[0].stateBits = GLS_DEFAULT;

        stages[1].bundle[0].image[0] = image;
        stages[1].active = qtrue;
        stages[1].rgbGen = CGEN_IDENTITY;
        stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
		shader.glShader = &glShader_lightmapNormal;
    }

    sh = FinishShader();
    return sh->index;
}

/*
====================
RE_RegisterShader
 
This is the exported shader entry point for the rest of the system
It will always return an index that will be valid.
 
This should really only be used for explicit shaders, because there is no
way to ask for different implicit lighting modes (vertex, lightmap, etc)
====================
*/
static qhandle_t _RE_RegisterShader(const char *name, int lightmapIndex, qboolean mipmap)
{
    shader_t	*sh;

    if (strlen(name) >= MAX_QPATH)
    {
        Com_Printf("Shader name exceeds MAX_QPATH\n");
        return 0;
    }

    sh = R_FindShader(name, lightmapIndex, mipmap);

    return (sh->defaultShader) ? 0 : sh->index;
}

qhandle_t RE_RegisterShaderLightMap(const char *name, int lightmapIndex)
{
	return _RE_RegisterShader(name, lightmapIndex, qtrue);
}

qhandle_t RE_RegisterShader(const char *name)
{
    return _RE_RegisterShader(name, LIGHTMAP_2D, qtrue);
}

qhandle_t RE_RegisterShaderNoMip(const char *name)
{
    return _RE_RegisterShader(name, LIGHTMAP_2D, qfalse);
}

/*
====================
R_GetShaderByHandle
 
When a handle is passed in by another module, this range checks
it and returns a valid (possibly default) shader_t to be used internally.
====================
*/
shader_t *R_GetShaderByHandle(qhandle_t hShader)
{
    if (hShader < 0 || hShader >= tr.numShaders)
    {
        CL_RefPrintf(PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader); // bk: FIXME name
        return tr.defaultShader;
    }
    return tr.shaders[hShader];
}

/*
===============
R_ShaderList_f
 
Dump information on all valid shaders to the console
A second parameter will cause it to print in sorted order
===============
*/
void	R_ShaderList_f (void)
{
    int			i;
    int			count = 0;
    shader_t	*shader, **shaders = (Cmd_Argc() > 1) ? tr.sortedShaders : tr.shaders;

    CL_RefPrintf (PRINT_ALL, "-----------------------\n");

    for (i=0; i<tr.numShaders ; i++)
    {
        shader = shaders[i];

        CL_RefPrintf(PRINT_ALL, "%i %s", shader->numUnfoggedPasses, (shader->lightmapIndex >= 0) ? "L " : "  ");

        if (shader->explicitlyDefined)
            CL_RefPrintf(PRINT_ALL, "E ");
        else
            CL_RefPrintf(PRINT_ALL, "  ");

        if(shader->glShader && r_glsl_render->integer)
            CL_RefPrintf(PRINT_ALL, "ogls");
        else if(shader->optimalStageIteratorFunc == RB_StageIteratorGeneric)
            CL_RefPrintf(PRINT_ALL, "gen ");
        else if(shader->optimalStageIteratorFunc == RB_StageIteratorSky)
            CL_RefPrintf(PRINT_ALL, "sky ");
        else if(shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitexture)
            CL_RefPrintf(PRINT_ALL, "lmmt");
        else if(shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTexture)
            CL_RefPrintf(PRINT_ALL, "vlt ");
        else
            CL_RefPrintf(PRINT_ALL, "    ");

        CL_RefPrintf(PRINT_ALL,  ": %s%s\n", shader->name, (shader->defaultShader) ? " (DEFAULTED)" : "");

        count++;
    }

    CL_RefPrintf (PRINT_ALL, "%i total shaders\n", count);
    CL_RefPrintf (PRINT_ALL, "------------------\n");
}


/*
====================
ScanAndLoadShaderFiles
 
Finds and loads all .shader files, combining them into
a single large text block that can be scanned for shader names
=====================
*/
#define	MAX_SHADER_FILES	4096
static void ScanAndLoadShaderFiles(void)
{
    char **shaderFiles;
    char *buffers[MAX_SHADER_FILES];
    char *p;
    int numShaders;
    int i;
    char *oldp, *token, *hashMem;
    int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;

    long sum = 0;
    // scan for shader files
    shaderFiles = FS_ListFiles("scripts", ".shader", &numShaders);

    if (!shaderFiles || !numShaders)
    {
        CL_RefPrintf(PRINT_WARNING, "WARNING: no shader files found\n");
        return;
    }

    if (numShaders > MAX_SHADER_FILES)
        numShaders = MAX_SHADER_FILES;

    // load and parse shader files
    for (i = 0; i < numShaders; i++)
    {
        char filename[MAX_QPATH];

        Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]);
        CL_RefPrintf(PRINT_ALL, "...loading '%s'\n", filename);
        sum += FS_ReadFile(filename, (void **)&buffers[i]);
        if (!buffers[i])
            Com_Error(ERR_DROP, "Couldn't load %s", filename);
    }

    // build single large buffer
    s_shaderText = (char *)Hunk_Alloc(sum + numShaders*2, h_low);

    // free in reverse order, so the temp files are all dumped
    for (i = numShaders - 1; i >= 0 ; i--)
    {
        strcat(s_shaderText, "\n");
        p = &s_shaderText[strlen(s_shaderText)];
        strcat(s_shaderText, buffers[i]);
        FS_FreeFile(buffers[i]);
        buffers[i] = p;
        COM_Compress(p);
    }

    // free up memory
    FS_FreeFileList(shaderFiles);

    Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
    size = 0;
    //
    for (i = 0; i < numShaders; i++)
    {
        // pointer to the first shader file
        p = buffers[i];
        // look for label
        while (1)
        {
            token = COM_ParseExt(&p, qtrue);
            if (token[0] == 0)
                break;

            hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
            shaderTextHashTableSizes[hash]++;
            size++;
            SkipBracedSection(&p);
            // if we passed the pointer to the next shader file
            if (i < numShaders - 1 && p > buffers[i+1])
				break;
        }
    }

    size += MAX_SHADERTEXT_HASH;

    hashMem = (char *)Hunk_Alloc(size * sizeof(char *), h_low);

    for (i = 0; i < MAX_SHADERTEXT_HASH; i++)
    {
        shaderTextHashTable[i] = (char **) hashMem;
        hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *));
    }

    Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
    //
    for (i = 0; i < numShaders; i++)
    {
        // pointer to the first shader file
        p = buffers[i];
        // look for label
        while (1)
        {
            oldp = p;
            token = COM_ParseExt(&p, qtrue);
            if (token[0] == 0)
                break;

            hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
            shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp;

            SkipBracedSection(&p);
            // if we passed the pointer to the next shader file
            if (i < numShaders - 1 && p > buffers[i+1])
				break;
        }
    }

    return;
}

/*
====================
CreateInternalShaders
====================
*/
static void CreateInternalShaders(void)
{
    tr.numShaders = 0;

    // init the default shader
    Com_Memset(&shader, 0, sizeof(shader));
    Com_Memset(&stages, 0, sizeof(stages));

    Q_strncpyz(shader.name, "<default>", sizeof(shader.name));

    shader.lightmapIndex = LIGHTMAP_NONE;
    stages[0].bundle[0].image[0] = tr.defaultImage;
    stages[0].active = qtrue;
    stages[0].stateBits = GLS_DEFAULT;
    tr.defaultShader = FinishShader();

    // shadow shader is just a marker
    Q_strncpyz(shader.name, "<stencil shadow>", sizeof(shader.name));
    shader.sort = SS_STENCIL_SHADOW;
    tr.shadowShader = FinishShader();
}

static void CreateExternalShaders(void)
{
    tr.projectionShadowShader = R_FindShader("projectionShadow", LIGHTMAP_NONE, qtrue);
    tr.flareShader = R_FindShader("flareShader", LIGHTMAP_NONE, qtrue);
    tr.sunShader = R_FindShader("sun", LIGHTMAP_NONE, qtrue);
}

/*
==================
R_InitShaders
==================
*/
void R_InitShaders(void)
{
    CL_RefPrintf(PRINT_ALL, "Initializing Shaders\n");

    Com_Memset(hashTable, 0, sizeof(hashTable));

    deferLoad = qfalse;

    CreateInternalShaders();

    ScanAndLoadShaderFiles();

    CreateExternalShaders();
}

void R_DeleteShaders(void)
{
    int			i;
    shader_t	*shader, **shaders = (Cmd_Argc() > 1) ? tr.sortedShaders : tr.shaders;

    for (i=0; i<tr.numShaders ; i++)
    {
        shader = shaders[i];
		if(shader->glShader)
			Shader_Free(shader->glShader);
		shader->glShader = NULL;
	}
}
